package car_rental.dao;

import java.util.Arrays;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Root;

import car_rental.entities.Dto;
import car_rental.exceptions.DuplicateException;

public abstract class AbstractDao<T extends Dto> extends TransactionalDao {
	private final Class<T> clazz;

	protected abstract void update(final T stored, final T item);
	
	protected AbstractDao(final Class<T> aClazz) {
		clazz = aClazz;
	}
	
	public final void persist(final T item, final int parentId) throws DuplicateException {
		persistAll(Arrays.asList(item), parentId);
	}
	
	protected void preparePersistWithParent(final List<T> items, final int parentId)
			throws DuplicateException {
		throw new UnsupportedOperationException();
	}
	
	protected void loadEager(final T item) {
		throw new UnsupportedOperationException();
	}
	
	protected void checkDuplicate(final T item) throws DuplicateException {
	}
	
	public final void persistAll(final List<T> items, final int parentId) throws DuplicateException {
		preparePersistWithParent(items, parentId);
		persistAll(items);
	}
	
	public void persist(final T item) throws DuplicateException {
		throw new UnsupportedOperationException();
	}
	
	public void persistAll(final List<T> items) {
		try {
			doTransactional(new Transactional<Void>() {
				@Override
				public Void run(EntityManager entityManager) throws Exception {
					for (final T item : items) {
						entityManager.persist(item);
					}
					return null;
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public final T find(final int id) {
		try {
			return doTransactional(new Transactional<T>() {
				@Override
				public T run(EntityManager entityManager) throws Exception {
					return entityManager.find(clazz, id);
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public T findAndLoad(final int id) {
		try {
			return doTransactional(new Transactional<T>() {
				@Override
				public T run(EntityManager entityManager) throws Exception {
					final T stored = entityManager.find(clazz, id);
					loadEager(stored);
					return stored;
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public final List<T> getAll() {
		try {
			return doTransactional(new Transactional<List<T>>() {
				@Override
				public List<T> run(EntityManager entityManager) throws Exception {
					final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
					final CriteriaQuery<T> queryPallet = builder.createQuery(clazz);
					final Root<T> fromPallet = queryPallet.from(clazz);
					queryPallet.select(fromPallet);
					return entityManager.createQuery(queryPallet).getResultList();
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
	
	public final void update(final T item) {
		try {
			doTransactional(new Transactional<Void>() {
				@Override
				public Void run(EntityManager entityManager) throws Exception {
					final T stored = entityManager.find(clazz, item.getId());
					update(stored, item);
					entityManager.persist(stored);
					return null;
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public final void remove(final int id) {
		try {
			doTransactional(new Transactional<Void>() {
				@Override
				public Void run(EntityManager entityManager) throws Exception {
					final T stored = entityManager.find(clazz, id);
					entityManager.remove(stored);
					return null;
				}
			});
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
